package com.ld.security.config;

import jakarta.annotation.Nonnull;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * Filter:过滤器是隶属web容器，由servlet提供，定义一个过滤器需要实现Filter接口
 * Interceptor：拦截器是隶属Spring框架的，定义一个拦截器需要实现HandlerInterceptor接口
 * <p>
 * <p>
 * 调用顺序：先调用过滤器，在调用拦截器，因为过滤器属于Web容器，拦截器属于spring框架。
 * 作用范围：过滤器拦截所有Url请求，拦截器只拦截spring框架中controller层的方法
 * 功能：
 * 过滤器主要用于对请求进行预处理和后处理，如请求编码转换、参数过滤、请求日志记录等。
 * 拦截器主要用于对请求进行拦截和拦截后的处理，如权限验证、登录验证、日志记录等。
 * 调用方式：
 * 过滤器通过在web.xml文件中配置进行调用，对请求进行预处理和后处理。
 * 拦截器则是通过框架提供的注解或配置方式进行调用。
 *
 *
 *
 * <p>
 * jwt Filter config
 * <p>
 * OncePerRequestFilter: 是过滤器的基类，每个请求仅调用一次。
 * <p>
 * 协作流程
 * 请求到达Servlet容器
 * <p>
 * Servlet容器调用所有注册的过滤器，包括 DelegatingFilterProxy。
 * <p>
 * 委托给FilterChainProxy
 * <p>
 * DelegatingFilterProxy 从Spring上下文中获取 FilterChainProxy 实例，并调用其 doFilter() 方法。
 * <p>
 * 选择SecurityFilterChain
 * <p>
 * FilterChainProxy 遍历所有 SecurityFilterChain，通过 requestMatcher 匹配当前请求的URL，选择第一个匹配的链。
 * <p>
 * 执行过滤器链
 * <p>
 * 执行选定 SecurityFilterChain 中的所有过滤器（如 UsernamePasswordAuthenticationFilter、AuthorizationFilter）。
 * <p>
 * 返回响应
 * <p>
 * 若所有过滤器通过，请求到达目标资源；若被拦截，返回401/403等错误。
 * <p>
 * <p>
 * <p>
 * DelegatingFilterProxy：Servlet过滤器入口，委托给Spring的 FilterChainProxy。
 * <p>
 * FilterChainProxy：Spring安全核心，管理多个 SecurityFilterChain 并选择执行。
 * <p>
 * SecurityFilterChain：安全规则的最小单元，定义URL匹配条件和过滤器集合。
 */
@Component
@RequiredArgsConstructor//替代@AutoWired 在注入的时候需要用final定义，或者使用@notnull（@nonnull）注解，采用的是构造器注入方式
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    public final static String AUTHORIZATION = "Authorization";
    private final static String STARTS_WITH = "bearer ";
    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(
            @Nonnull HttpServletRequest request,
            @Nonnull HttpServletResponse response,
            @Nonnull FilterChain filterChain
    ) throws ServletException, IOException {

        String requestURI = request.getRequestURI();

        final String authorization = request.getHeader(AUTHORIZATION);
        final String jwt;
        final String userEmail;

//        如果没有token，或者不是本系统指定开头的token，则交予下一个过滤器处理
        if (authorization == null || !authorization.startsWith(STARTS_WITH)) {
            filterChain.doFilter(request, response);
            return;
        }

        jwt = authorization.substring(7);
        userEmail = jwtService.extractUsername(jwt);

        if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//            当前用户不为空，并且还未经过身份验证（未经过身份验证的getAuthentication() 为null）
            UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);

//            如果令牌是有效的
            if (jwtService.isTokenValid(jwt, userDetails)) {

//                if中的三行代码很重要，整体逻辑就是令牌是解析后有效的
//                将已经登录的用户信息封装成UsernamePasswordAuthenticationToken
//                设置令牌的来源为request
//                手动设置security上下文，免除复杂的登录验证

//                将数据库中的用户封装为 UsernamePasswordAuthenticationToken

//                以下三行代码是手动登录security，因为当前过滤器是配置在UsernamePasswordAuthenticationFilter之前，
//                UsernamePasswordAuthenticationFilter过滤器并没有停止使用，在token验证通过后直接进行手动登录即可通过
//                UsernamePasswordAuthenticationFilter过滤器的验证
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails,
                        null,
                        userDetails.getAuthorities()
                );

//                将request中的令牌封装为UsernamePasswordAuthenticationToken，并且令牌来源为request
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

//                设置上下文的authenticationToken，让当前身份在security的上下文中合法（第一次进行登录验证，以后每次请求都是使用token就行了，所以手动将当前token置为合法的）
//                也就是使用用户信息构建authentication放到SecurityContextHolder就等于用户已经登录了，就不用在通过security的登录流程了
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }

            filterChain.doFilter(request, response);
        }
    }
}
